﻿
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;

public class AsyncHttpRequest
{
    #region 是否信任无效证书

    public static void TrustInvalidCertificate(bool trust)
    {
        if (trust)
        {
            ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
        }
        else
        {
            ServicePointManager.ServerCertificateValidationCallback = null;
        }
    }
    private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        return true;
    }
    #endregion

    
    private readonly HttpWebRequest m_WebRequest;
    public HttpWebRequest webRequest => m_WebRequest;

    public Action<AsyncHttpRequest> onResponse;

    private IAsyncResult m_IAsyncResult;
    private string m_PostContent;

    private string m_Error;
    private string m_Response;

    public string error => m_Error;
    public string response => m_Response;
    
    public AsyncHttpRequest(string url)
    {
        m_WebRequest = WebRequest.CreateHttp(url);
    }

    public static string CombineQueryUrl (string url, object queryParamas)
    {
        if (queryParamas == null)
        {
            return url;
        }

        Type type = queryParamas.GetType();
        System.Reflection.FieldInfo[] fields = type.GetFields(System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
        if (fields.Length == 0)
        {
            return url;
        }

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append('?');

        for (int i = 0, count = fields.Length; i < count; ++i)
        {
            if (i > 0)
            {
                stringBuilder.Append('&');
            }

            string key = fields[i].Name;
            object value = fields[i].GetValue(queryParamas);
            string valueString = value == null ? "" : value.ToString();
            stringBuilder.AppendFormat("{0}={1}", key, valueString);
        }

        return url + stringBuilder.ToString();
    }

    public void AddHeaders(object o)
    {
        if (o == null)
        {
            return;
        }
        
        Type type = o.GetType();
        System.Reflection.FieldInfo[] fields = type.GetFields(System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
        if (fields.Length == 0)
        {
            return;
        }

        for (int i = 0, count = fields.Length; i < count; ++i)
        {
            string headerName = fields[i].Name;
            object headerValue = fields[i].GetValue(o);
            string headerValueString = headerValue == null ? "" : headerValue.ToString();
            GameDebug.Log($"Add Header, {headerName} : {headerValueString}");
            this.m_WebRequest.Headers.Add(headerName, headerValueString);
        }
    }
    
    public void GET()
    {
        try
        {
            // 增加超时处理
            this.AddTimeoutHandler();
            
            m_WebRequest.Method = "GET";
            m_IAsyncResult = m_WebRequest.BeginGetResponse(OnGetResponse, null);
        }
        catch (Exception e)
        {
            GameDebug.LogError($"web request GET error: {e}");
        }
    }

    /// <summary>
    /// 表单方式
    /// </summary>
    public void POSTForm(object o)
    {
        try
        {
            if (o == null)
            {
                GameDebug.Log("Form empty");
                this.POST();
            }
            else
            {
                string form = HttpRequestForm.GetString(o);
                GameDebug.Log($"POST Form: {form}");
                this.POST(form);
            }
        }
        catch (Exception e)
        {
            GameDebug.LogError($"web request POSTForm error: {e}");
        }
    }

    public void POST(string content)
    {
        if (content == null)
        {
            throw new Exception("content can not be null");
        }

        try
        {
            // 增加超时处理
            this.AddTimeoutHandler();
            
            m_WebRequest.Method = "POST";
            m_PostContent = content;
            m_IAsyncResult = m_WebRequest.BeginGetRequestStream(OnGetRequestStream, null);
        }
        catch (Exception e)
        {
            GameDebug.LogError($"web request POST error: {e}");
        }
    }

    public void POST()
    {
        // 增加超时处理
        this.AddTimeoutHandler();
        
        m_WebRequest.Method = "POST";
        m_WebRequest.ContentLength = 0;
        m_IAsyncResult = m_WebRequest.BeginGetResponse(OnGetResponse, null);
    }


    private void OnGetRequestStream(IAsyncResult ar)
    {
        if (m_IAsyncResult == null)
        {
            return;
        }

        if (ar != m_IAsyncResult)
        {
            GameDebug.LogError("OnGetRequestStream IAsyncResult not equal");
            return;
        }

        Stream stream = null;
        try
        {
            stream = m_WebRequest.EndGetRequestStream(ar);
            byte[] data = Encoding.UTF8.GetBytes(m_PostContent);
            stream.Write(data, 0, data.Length);
            
            m_IAsyncResult = m_WebRequest.BeginGetResponse(OnGetResponse, null);
        }
        catch (Exception e)
        {
            this.FinishWithError(e.Message);
        }
        finally
        {
            stream?.Close();
        }
    }

    private void OnGetResponse(IAsyncResult ar)
    {
        if (m_IAsyncResult == null)
        {
            return;
        }
        
        if (ar != m_IAsyncResult)
        {
            GameDebug.LogError("OnGetResponse IAsyncResult not equal");
            return;
        }

        WebResponse webResponse = null;
        Stream stream = null;
        StreamReader streamReader = null;
        try
        {
            webResponse = m_WebRequest.EndGetResponse(ar);
            //GameDebug.Log(webResponse.GetType().Name);// -> HttpWebResponse

            stream = webResponse.GetResponseStream();
            if (stream == null)
            {
                this.FinishWithError("response stream null");
                return;
            }

            streamReader = new StreamReader(stream);
            string res = streamReader.ReadToEnd();
            
            this.FinishWithResponse(res);
        }
        catch (Exception e)
        {
            //GameDebug.LogError(e);
            this.FinishWithError($"{e.Message} url: {m_WebRequest.RequestUri}");
        }
        finally
        {
            streamReader?.Close();
            stream?.Close();
            webResponse?.Dispose();
        }
    }

    private void FinishWithError(string err)
    {
        if (m_Finished)
        {
            return;
        }

        m_Finished = true;
        
        m_Error = err;
        this.onResponse?.Invoke(this);
    }

    private void FinishWithResponse(string res)
    {
        if (m_Finished)
        {
            return;
        }

        m_Finished = true;
        
        m_Response = res;
        this.onResponse?.Invoke(this);
    }

    #region For Ex Time out

    // 超时处理 (默认10秒)
    private int m_Timeout = 10000;

    /// <summary>
    /// 超时处理 (默认10秒)
    /// </summary>
    /// <exception cref="Exception"></exception>
    public int Timeout
    {
        get { return m_Timeout; }
        set
        {
            if (value <= 0)
            {
                throw new Exception($"timeout {value} <= 0");
            }

            m_Timeout = value;
        }
    }
    private volatile bool m_Finished = false;
    
    private void AddTimeoutHandler()
    {
        DelayDoAction.Instance.Add(OnExTimeout, this.Timeout);
    }

    private void OnExTimeout()
    {
        if (!m_Finished)
        {
            m_IAsyncResult = null; // 移除回调
            //this.FinishWithError("ext web request time out");
            this.FinishWithError("504 Gateway Timeout");
        }
    }

    #endregion
    

}
